1 /** 2 * Cyclic Buffer 3 * Copyright: © 2016 Economic Modeling Specialists, Intl. 4 * Authors: Nickolay Bukreyev 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 8 module containers.cyclicbuffer; 9 10 private import core.exception : onRangeError; 11 private import std.experimental.allocator.mallocator : Mallocator; 12 private import std.range.primitives : empty, front, back, popFront, popBack; 13 private import containers.internal.node : shouldAddGCRange; 14 15 /** 16 * Array that provides constant time (amortized) appending and popping 17 * at either end, as well as random access to the elements. 18 * 19 * Params: 20 * T = the array element type 21 * Allocator = the allocator to use. Defaults to `Mallocator`. 22 * supportGC = true if the container should support holding references to GC-allocated memory. 23 */ 24 struct CyclicBuffer(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) 25 { 26 @disable this(this); 27 28 private import std.conv : emplace; 29 private import std.experimental.allocator.common : stateSize; 30 private import std.traits : isImplicitlyConvertible, hasElaborateDestructor; 31 32 static if (stateSize!Allocator != 0) 33 { 34 /// No default construction if an allocator must be provided. 35 @disable this(); 36 37 /** 38 * Use the given `allocator` for allocations. 39 */ 40 this(Allocator allocator) nothrow pure @safe @nogc 41 in 42 { 43 assert(allocator !is null, "Allocator must not be null"); 44 } 45 do 46 { 47 this.allocator = allocator; 48 } 49 } 50 51 ~this() 52 { 53 clear(); 54 static if (useGC) 55 { 56 import core.memory : GC; 57 GC.removeRange(storage.ptr); 58 } 59 allocator.deallocate(storage); 60 } 61 62 /** 63 * Removes all contents from the buffer. 64 */ 65 void clear() 66 { 67 if (!empty) 68 { 69 static if (hasElaborateDestructor!T) 70 { 71 if (start <= end) 72 foreach (ref item; storage[start .. end + 1]) 73 .destroy(item); 74 else 75 { 76 foreach (ref item; storage[start .. $]) 77 .destroy(item); 78 foreach (ref item; storage[0 .. end + 1]) 79 .destroy(item); 80 } 81 } 82 start = (end + 1) % capacity; 83 _length = 0; 84 } 85 } 86 87 /** 88 * Ensures capacity is at least as large as specified. 89 */ 90 size_t reserve(size_t newCapacity) 91 { 92 immutable oldCapacity = capacity; 93 if (newCapacity <= oldCapacity) 94 return oldCapacity; 95 auto old = storage; 96 if (oldCapacity == 0) 97 storage = cast(typeof(storage)) allocator.allocate(newCapacity * T.sizeof); 98 else 99 { 100 auto a = cast(void[]) old; 101 allocator.reallocate(a, newCapacity * T.sizeof); 102 storage = cast(typeof(storage)) a; 103 } 104 static if (useGC) 105 { 106 import core.memory : GC; 107 //Add, then remove. Exactly in that order. 108 GC.addRange(storage.ptr, newCapacity * T.sizeof); 109 GC.removeRange(old.ptr); 110 } 111 if (empty) 112 end = (start - 1 + capacity) % capacity; 113 else if (start > end) 114 { 115 //The buffer was wrapped around prior to reallocation. 116 117 //`moveEmplaceAll` is only available in 2.069+, so use a low level alternative. 118 //Even more, we don't have to .init the moved away data, because we don't .destroy it. 119 import core.stdc..string : memcpy, memmove; 120 immutable prefix = end + 1; 121 immutable suffix = oldCapacity - start; 122 if (prefix <= suffix) 123 { 124 //The prefix is being moved right behind of suffix. 125 immutable space = newCapacity - oldCapacity; 126 if (space >= prefix) 127 { 128 memcpy(storage.ptr + oldCapacity, storage.ptr, prefix * T.sizeof); 129 end += oldCapacity; 130 } 131 else 132 { 133 //There is not enough space, so move what we can, 134 //and shift the rest to the start of the buffer. 135 memcpy(storage.ptr + oldCapacity, storage.ptr, space * T.sizeof); 136 end -= space; 137 memmove(storage.ptr, storage.ptr + space, (end + 1) * T.sizeof); 138 } 139 } 140 else 141 { 142 //The suffix is being moved forward, to the end of the buffer. 143 //Due to the fact that these locations may overlap, use `memmove`. 144 memmove(storage.ptr + newCapacity - suffix, storage.ptr + start, suffix * T.sizeof); 145 start = newCapacity - suffix; 146 } 147 //Ensure everything is still alright. 148 if (start <= end) 149 assert(end + 1 - start == length); 150 else 151 assert(end + 1 + (newCapacity - start) == length); 152 } 153 return capacity; 154 } 155 156 /** 157 * Inserts the given item into the start of the buffer. 158 */ 159 void insertFront(U)(U value) if (isImplicitlyConvertible!(U, T)) 160 { 161 if (empty) 162 reserve(4); 163 else if ((end + 1) % capacity == start) 164 reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2); 165 start = (start - 1 + capacity) % capacity; 166 _length++; 167 emplace(&storage[start], value); 168 } 169 170 /** 171 * Inserts the given item into the end of the buffer. 172 */ 173 void insertBack(U)(U value) if (isImplicitlyConvertible!(U, T)) 174 { 175 if (empty) 176 reserve(4); 177 else if ((end + 1) % capacity == start) 178 reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2); 179 end = (end + 1) % capacity; 180 _length++; 181 emplace(&storage[end], value); 182 } 183 184 /// ditto 185 alias insert = insertBack; 186 187 /// ditto 188 alias insertAnywhere = insertBack; 189 190 /// ditto 191 alias put = insertBack; 192 193 /** 194 * Removes the item at the start of the buffer. 195 */ 196 void removeFront() 197 { 198 version (assert) if (empty) onRangeError(); 199 size_t pos = start; 200 start = (start + 1) % capacity; 201 _length--; 202 static if (hasElaborateDestructor!T) 203 .destroy(storage[pos]); 204 } 205 206 /// ditto 207 alias popFront = removeFront; 208 209 /** 210 * Removes the item at the end of the buffer. 211 */ 212 void removeBack() 213 { 214 version (assert) if (empty) onRangeError(); 215 size_t pos = end; 216 end = (end - 1 + capacity) % capacity; 217 _length--; 218 static if (hasElaborateDestructor!T) 219 .destroy(storage[pos]); 220 } 221 222 /// ditto 223 alias popBack = removeBack; 224 225 /// Accesses to the item at the start of the buffer. 226 auto ref front(this This)() nothrow pure @property @safe 227 { 228 version (assert) if (empty) onRangeError(); 229 alias ET = ContainerElementType!(This, T, true); 230 return cast(ET) storage[start]; 231 } 232 233 /// Accesses to the item at the end of the buffer. 234 auto ref back(this This)() nothrow pure @property @safe 235 { 236 version (assert) if (empty) onRangeError(); 237 alias ET = ContainerElementType!(This, T, true); 238 return cast(ET) storage[end]; 239 } 240 241 /// buffer[i] 242 auto ref opIndex(this This)(size_t i) nothrow pure @safe 243 { 244 version (assert) if (i >= length) onRangeError(); 245 alias ET = ContainerElementType!(This, T, true); 246 return cast(ET) storage[(start + i) % $]; 247 } 248 249 /// buffer[] 250 Range!This opIndex(this This)() nothrow pure @safe @nogc 251 { 252 if (empty) 253 return typeof(return)(storage[0 .. 0], storage[0 .. 0]); 254 if (start <= end) 255 return typeof(return)(storage[start .. end + 1], storage[0 .. 0]); 256 return typeof(return)(storage[start .. $], storage[0 .. end + 1]); 257 } 258 259 /// buffer[i .. j] 260 size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc 261 { 262 return [i, j]; 263 } 264 265 /// ditto 266 Range!This opIndex(this This)(size_t[2] indices) nothrow pure @safe 267 { 268 size_t i = indices[0], j = indices[1]; 269 version (assert) 270 { 271 if (i > j) onRangeError(); 272 if (j > length) onRangeError(); 273 } 274 if (i == j) 275 return typeof(return)(storage[0 .. 0], storage[0 .. 0]); 276 i = (start + i) % capacity; 277 j = (start + j) % capacity; 278 if (i < j) 279 return typeof(return)(storage[i .. j], storage[0 .. 0]); 280 return typeof(return)(storage[i .. $], storage[0 .. j]); 281 } 282 283 static struct Range(ThisT) 284 { 285 private 286 { 287 static if (is(ThisT == immutable)) 288 { 289 alias SliceT = immutable(ContainerStorageType!T)[]; 290 } 291 else static if (is(ThisT == const)) 292 { 293 alias SliceT = const(ContainerStorageType!T)[]; 294 } 295 else 296 { 297 alias SliceT = ContainerStorageType!T[]; 298 } 299 } 300 301 @disable this(); 302 303 this(SliceT a, SliceT b) nothrow pure @safe @nogc 304 { 305 head = a; 306 tail = b; 307 } 308 309 This save(this This)() nothrow pure @property @safe @nogc 310 { 311 return this; 312 } 313 314 bool empty() const nothrow pure @property @safe @nogc 315 { 316 return head.empty && tail.empty; 317 } 318 319 size_t length() const nothrow pure @property @safe @nogc 320 { 321 return head.length + tail.length; 322 } 323 324 alias opDollar = length; 325 326 auto ref front(this This)() nothrow pure @property @safe 327 { 328 if (!head.empty) 329 return cast(ET) head.front; 330 return cast(ET) tail.front; 331 } 332 333 auto ref back(this This)() nothrow pure @property @safe 334 { 335 if (!tail.empty) 336 return cast(ET) tail.back; 337 return cast(ET) head.back; 338 } 339 340 void popFront() nothrow pure @safe 341 { 342 if (head.empty) 343 { 344 import std.algorithm.mutation : swap; 345 //Always try to keep `head` non-empty. 346 swap(head, tail); 347 } 348 head.popFront(); 349 } 350 351 void popBack() nothrow pure @safe 352 { 353 if (!tail.empty) 354 tail.popBack(); 355 else 356 head.popBack(); 357 } 358 359 /// range[i] 360 auto ref opIndex(this This)(size_t i) nothrow pure @safe 361 { 362 return cast(ET) (i < head.length ? head[i] : tail[i - head.length]); 363 } 364 365 /// range[] 366 This opIndex(this This)() nothrow pure @safe @nogc 367 { 368 return this.save; 369 } 370 371 /// range[i .. j] 372 size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc 373 { 374 return [i, j]; 375 } 376 377 /// ditto 378 This opIndex(this This)(size_t[2] indices) nothrow pure @safe 379 { 380 size_t i = indices[0], j = indices[1]; 381 version (assert) 382 { 383 if (i > j) onRangeError(); 384 if (j > length) onRangeError(); 385 } 386 if (i >= head.length) 387 return typeof(return)(tail[i - head.length .. j - head.length], tail[0 .. 0]); 388 if (j <= head.length) 389 return typeof(return)(head[i .. j], head[0 .. 0]); 390 return typeof(return)(head[i .. $], tail[0 .. j - head.length]); 391 } 392 393 /// range[...]++ 394 auto ref opUnary(string op)() nothrow pure @safe @nogc 395 if (op == "++" || op == "--") 396 { 397 mixin(op ~ "head[];"); 398 mixin(op ~ "tail[];"); 399 return this; 400 } 401 402 /// range[...] = value 403 auto ref opAssign(U)(const auto ref U value) nothrow pure @safe @nogc 404 { 405 head[] = value; 406 tail[] = value; 407 return this; 408 } 409 410 /// range[...] += value 411 auto ref opOpAssign(string op, U)(const auto ref U value) nothrow pure @safe @nogc 412 { 413 mixin("head[] " ~ op ~ "= value;"); 414 mixin("tail[] " ~ op ~ "= value;"); 415 return this; 416 } 417 418 private: 419 420 alias ET = ContainerElementType!(ThisT, T); 421 422 SliceT head, tail; 423 } 424 425 /// Returns: the number of items in the buffer. 426 size_t length() const nothrow pure @property @safe @nogc { return _length; } 427 428 /// ditto 429 alias opDollar = length; 430 431 /// Returns: maximal number of items the buffer can hold without reallocation. 432 size_t capacity() const nothrow pure @property @safe @nogc { return storage.length; } 433 434 /// Returns: whether or not the CyclicBuffer is empty. 435 bool empty() const nothrow pure @property @safe @nogc { return length == 0; } 436 437 private: 438 439 import containers.internal.storage_type : ContainerStorageType; 440 import containers.internal.element_type : ContainerElementType; 441 import containers.internal.mixins : AllocatorState; 442 443 enum bool useGC = supportGC && shouldAddGCRange!T; 444 mixin AllocatorState!Allocator; 445 ContainerStorageType!T[] storage; 446 size_t start, end, _length; 447 } 448 449 version(emsi_containers_unittest) private 450 { 451 import std.algorithm.comparison : equal; 452 import std.experimental.allocator.gc_allocator : GCAllocator; 453 import std.experimental.allocator.building_blocks.free_list : FreeList; 454 import std.range : iota, lockstep, StoppingPolicy; 455 456 struct S 457 { 458 int* a; 459 460 ~this() 461 { 462 (*a)++; 463 } 464 } 465 466 class C 467 { 468 int* a; 469 470 this(int* a) 471 { 472 this.a = a; 473 } 474 475 ~this() 476 { 477 (*a)++; 478 } 479 } 480 } 481 482 version(emsi_containers_unittest) unittest 483 { 484 static void test(int size) 485 { 486 { 487 CyclicBuffer!int b; 488 assert(b.empty); 489 foreach (i; 0 .. size) 490 { 491 assert(b.length == i); 492 b.insertBack(i); 493 assert(b.back == i); 494 } 495 assert(b.length == size); 496 foreach (i; 0 .. size) 497 { 498 assert(b.length == size - i); 499 assert(b.front == i); 500 b.removeFront(); 501 } 502 assert(b.empty); 503 } 504 { 505 CyclicBuffer!int b; 506 foreach (i; 0 .. size) 507 { 508 assert(b.length == i); 509 b.insertFront(i); 510 assert(b.front == i); 511 } 512 assert(b.length == size); 513 foreach (i; 0 .. size) 514 { 515 assert(b.length == size - i); 516 assert(b.back == i); 517 b.removeBack(); 518 } 519 assert(b.empty); 520 } 521 } 522 523 foreach (size; [1, 2, 3, 4, 5, 7, 8, 9, 512, 520, 0x10000, 0x10001, 0x20000]) 524 test(size); 525 } 526 527 version(emsi_containers_unittest) unittest 528 { 529 static void test(int prefix, int suffix, int newSize) 530 { 531 CyclicBuffer!int b; 532 foreach_reverse (i; 0 .. suffix) 533 b.insertFront(i); 534 foreach (i; suffix .. suffix + prefix) 535 b.insertBack(i); 536 assert(b.length == prefix + suffix); 537 b.reserve(newSize); 538 assert(b.length == prefix + suffix); 539 assert(equal(b[], iota(prefix + suffix))); 540 } 541 542 immutable prefixes = [2, 3, 3, 4, 4]; 543 immutable suffixes = [3, 2, 4, 3, 4]; 544 immutable sizes = [16, 16, 9, 9, 9]; 545 546 foreach (a, b, c; lockstep(prefixes, suffixes, sizes, StoppingPolicy.requireSameLength)) 547 test(a, b, c); 548 } 549 550 version(emsi_containers_unittest) unittest 551 { 552 int* a = new int; 553 { 554 CyclicBuffer!S b; 555 { 556 S s = { a }; 557 foreach (i; 0 .. 5) 558 b.insertBack(s); 559 assert(*a == 5); 560 foreach (i; 0 .. 5) 561 b.insertBack(S(a)); 562 assert(*a == 10); 563 foreach (i; 0 .. 5) 564 { 565 b.removeBack(); 566 b.removeFront(); 567 } 568 assert(*a == 20); 569 } 570 assert(*a == 21); 571 } 572 assert(*a == 21); 573 } 574 575 version(emsi_containers_unittest) unittest 576 { 577 int* a = new int; 578 CyclicBuffer!C b; 579 { 580 C c = new C(a); 581 foreach (i; 0 .. 10) 582 b.insertBack(c); 583 assert(*a == 0); 584 foreach (i; 0 .. 5) 585 { 586 b.removeBack(); 587 b.removeFront(); 588 } 589 foreach (i; 0 .. b.capacity) 590 b.insertFront(null); 591 assert(*a == 0); 592 } 593 string s = ""; 594 foreach (i; 0 .. 1_000) 595 s = s ~ 'a'; 596 s = ""; 597 import core.memory : GC; 598 GC.collect(); 599 assert(*a == 0 || *a == 1); 600 } 601 602 version(emsi_containers_unittest) unittest 603 { 604 CyclicBuffer!int b; 605 b.insertFront(10); 606 assert(b[0] == 10); 607 b.insertFront(20); 608 assert(b[0] == 20); 609 assert(b[1] == 10); 610 b.insertFront(30); 611 assert(b[0] == 30); 612 assert(b[1] == 20); 613 assert(b[2] == 10); 614 b.insertBack(5); 615 assert(b[0] == 30); 616 assert(b[1] == 20); 617 assert(b[2] == 10); 618 assert(b[3] == 5); 619 b.back = 7; 620 assert(b[3] == 7); 621 } 622 623 version(emsi_containers_unittest) unittest 624 { 625 import std.range : isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange; 626 CyclicBuffer!int b; 627 static assert(isInputRange!(typeof(b[]))); 628 static assert(isForwardRange!(typeof(b[]))); 629 static assert(isBidirectionalRange!(typeof(b[]))); 630 static assert(isRandomAccessRange!(typeof(b[]))); 631 } 632 633 version(emsi_containers_unittest) unittest 634 { 635 CyclicBuffer!int b; 636 assert(b[].empty); 637 } 638 639 version(emsi_containers_unittest) unittest 640 { 641 FreeList!(Mallocator, 0, 64) alloc; 642 FreeList!(GCAllocator, 0, 64) alloc2; 643 auto b = CyclicBuffer!(int, typeof(&alloc))(&alloc); 644 auto b2 = CyclicBuffer!(int, typeof(&alloc2))(&alloc2); 645 auto b3 = CyclicBuffer!(int, GCAllocator)(); 646 } 647 648 version(emsi_containers_unittest) unittest 649 { 650 static void testConst(const ref CyclicBuffer!int b, int x) 651 { 652 assert(b[0] == x); 653 assert(b.front == x); 654 static assert(!__traits(compiles, { ++b[0]; } )); 655 assert(equal(b[], [x])); 656 } 657 658 CyclicBuffer!int b; 659 b.insertFront(0); 660 assert(b.front == 0); 661 b.front++; 662 assert(b[0] == 1); 663 b[0]++; 664 ++b[0]; 665 assert(b.front == 3); 666 assert(!b.empty); 667 b[0] *= 2; 668 assert(b[0] == 6); 669 testConst(b, 6); 670 b[]++; 671 assert(equal(b[], [7])); 672 b[0] = 5; 673 assert(b[0] == 5); 674 assert(b.front == 5); 675 testConst(b, 5); 676 assert(b[][0] == 5); 677 } 678 679 version(emsi_containers_unittest) unittest 680 { 681 int* a = new int; 682 { 683 CyclicBuffer!S b; 684 foreach (i; 0 .. 5) 685 b.insertBack(S(a)); 686 assert(*a == 5); 687 } 688 assert(*a == 10); 689 *a = 0; 690 { 691 CyclicBuffer!S b; 692 foreach (i; 0 .. 4) 693 b.insertBack(S(a)); 694 assert(*a == 4); 695 b.removeFront(); 696 assert(*a == 5); 697 b.insertBack(S(a)); 698 assert(*a == 6); 699 } 700 assert(*a == 10); 701 } 702 703 version(emsi_containers_unittest) unittest 704 { 705 CyclicBuffer!int b; 706 foreach (i; 0 .. 4) 707 b.insertBack(i); 708 b.removeFront(); 709 b.removeFront(); 710 b.insertBack(4); 711 b.insertBack(5); 712 assert(equal(b[], [2, 3, 4, 5])); 713 b.reserve(5); 714 assert(equal(b[], [2, 3, 4, 5])); 715 } 716 717 version(emsi_containers_unittest) unittest 718 { 719 CyclicBuffer!int b; 720 foreach (i; 0 .. 4) 721 b.insertBack(i); 722 b.removeFront(); 723 b.removeFront(); 724 b.removeFront(); 725 b.insertBack(4); 726 b.insertBack(5); 727 b.insertBack(6); 728 assert(equal(b[], [3, 4, 5, 6])); 729 b.reserve(5); 730 assert(equal(b[], [3, 4, 5, 6])); 731 } 732 733 version(emsi_containers_unittest) unittest 734 { 735 static void test(ref CyclicBuffer!int b) 736 { 737 assert(equal(b[], [4, 5, 6, 7, 8, 9, 10, 11])); 738 assert(b[3 .. 3].empty); 739 auto slice = b[1 .. 6]; 740 assert(equal(slice, [5, 6, 7, 8, 9])); 741 slice[3 .. 5] = 0; 742 assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11])); 743 slice[0 .. 2] += 1; 744 assert(equal(b[], [4, 6, 7, 7, 0, 0, 10, 11])); 745 slice[0 .. 2]--; 746 assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11])); 747 auto copy = slice.save; 748 assert(equal(slice, copy)); 749 assert(equal(slice, copy[])); 750 assert(slice.back == 0); 751 slice.popBack(); 752 assert(equal(slice, [5, 6, 7, 0])); 753 assert(slice.back == 0); 754 slice.popBack(); 755 assert(equal(slice, [5, 6, 7])); 756 assert(slice.back == 7); 757 slice.popBack(); 758 assert(equal(slice, [5, 6])); 759 assert(equal(copy, [5, 6, 7, 0, 0])); 760 slice[1] = 10; 761 assert(-copy[1] == -10); 762 copy[1] *= 2; 763 assert(slice[1] == 20); 764 assert(b[2] == 20); 765 auto copy2 = copy[0 .. $]; 766 assert(equal(copy, copy2)); 767 } 768 769 { 770 CyclicBuffer!int b; 771 foreach (i; 4 .. 12) 772 b.insertBack(i); 773 test(b); 774 } 775 { 776 CyclicBuffer!int b; 777 foreach (i; 0 .. 8) 778 b.insertBack(i); 779 foreach (i; 0 .. 4) 780 b.removeFront(); 781 foreach (i; 8 .. 12) 782 b.insertBack(i); 783 test(b); 784 } 785 } 786 787 version(emsi_containers_unittest) unittest 788 { 789 CyclicBuffer!int b; 790 foreach (i; 0 .. 10) 791 b.insertBack(i); 792 assert(b.capacity >= 10); 793 b.reserve(12); 794 assert(b.capacity >= 12); 795 } 796 797 version(emsi_containers_unittest) unittest 798 { 799 CyclicBuffer!int b; 800 foreach (i; 0 .. 6) 801 b.insertBack(i); 802 foreach (i; 6 .. 8) 803 b.insertFront(i); 804 assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5])); 805 b.reserve(b.capacity + 1); 806 assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5])); 807 } 808 809 version(emsi_containers_unittest) unittest 810 { 811 static class Foo 812 { 813 string name; 814 } 815 816 CyclicBuffer!Foo b; 817 }